define([], function() {

geometry2d = {};

geometry2d.Vector = function(x, y) {
  this.x = x;
  this.y = y;
};

geometry2d.Vector.prototype.toString = function() {
  return '[' + this.x + ',' + this.y + ']';
};

geometry2d.Vector.prototype.copy = function() {
  return new geometry2d.Vector(this.x, this.y);
};

geometry2d.Vector.prototype.add = function(vector) {
  this.x += vector.x;
  this.y += vector.y;
  return this;
};

geometry2d.Vector.prototype.subtract = function(vector) {
  this.x -= vector.x;
  this.y -= vector.y;
  return this;
};

geometry2d.Vector.prototype.scale = function(scalar) {
  this.x *= scalar;
  this.y *= scalar;
  return this;
};

geometry2d.Vector.prototype.normalize = function() {
  var length = this.length();
  this.x /= length;
  this.y /= length;
  return this;
};

/**
 * Rotates clockwise around the origin, where positive x is right and positive
 * y is up.
 */
geometry2d.Vector.prototype.rotate = function(revolutions) {
  var newX;
  var newY;
  if (revolutions == 1/4) {
    newX = this.y;
    newY = -this.x;
  } else if (revolutions == -1/4) {
    newX = -this.y;
    newY = this.x;
  } else if (revolutions == 1/2 || revolutions == -1/2) {
    newX = -this.x;
    newY = -this.y;
  } else {
    var theta = -revolutions * 2 * Math.PI;
    newX = (this.x * Math.cos(theta)) - (this.y * Math.sin(theta));
    newY = (this.x * Math.sin(theta)) + (this.y * Math.cos(theta));
  }
  this.x = newX;
  this.y = newY;
  return this;
};

geometry2d.Vector.prototype.flip = function() {
  return this.scale(-1);
};

geometry2d.Vector.prototype.length = function() {
  return Math.sqrt((this.x * this.x) + (this.y * this.y));
};

geometry2d.Vector.prototype.nonZero = function() {
  return (this.x != 0) || (this.y != 0);
}

geometry2d.zeroVector = function() {
  return new geometry2d.Vector(0, 0);
};

geometry2d.distance = function(vector1, vector2) {
  return vector1.copy().subtract(vector2).length();
};

geometry2d.dot = function(vector1, vector2) {
  return (vector1.x * vector2.x) + (vector1.y * vector2.y);
};

geometry2d.LineSegment = function(point1, point2) {
  this.point1 = point1;
  this.point2 = point2;
};

geometry2d.LineSegment.prototype.direction = function() {
  return this.point2.copy().subtract(this.point1).normalize();
};

/**
 * Returns a unit vector perpendicular to this vector, rotate 1/4 turn
 * counter-clockwise.
 */
geometry2d.LineSegment.prototype.normal = function() {
  return this.direction().rotate(-1/4);
};

geometry2d.LineSegment.prototype.length = function() {
  return geometry2d.distance(this.point1, this.point2);
};

geometry2d.LineSegment.prototype.lerp = function(t) {
  return this.point2.copy().subtract(this.point1).scale(t).add(this.point1);
};

/**
 * Arguments are Vector2d
 */
geometry2d.PolyLine = function(var_args) {
  this.points = [];
  for (var i = 0; i < arguments.length; i++) {
    this.points.push(arguments[i]);
  }

  this.length_ = 0;
  for (var i = 1; i < this.points.length; i++) {
    this.length_ += geometry2d.distance(this.points[i - 1], this.points[i]);
  }
};

/**
 * Total length of the polyline.
 */
geometry2d.PolyLine.prototype.length = function() {
  return this.length_;
};

/**
 * t is a parameter in the range [0, 1)
 */
geometry2d.PolyLine.prototype.segment = function(t) {
  var totalDistance = 0;
  for (var iSegment = 0; iSegment < this.points.length - 1; iSegment++) {
    totalDistance += geometry2d.distance(
      this.points[iSegment], this.points[iSegment + 1]);
    segmentEndT = totalDistance / this.length();
    if (t < segmentEndT) {
      return new geometry2d.LineSegment(
        this.points[iSegment], this.points[iSegment + 1]);
    }
  }
};

geometry2d.PolyLine.prototype.position = function(t) {
  var segmentBeginDistance = 0;
  for (var iSegment = 0; iSegment < this.points.length - 1; iSegment++) {
    var segment = new geometry2d.LineSegment(
      this.points[iSegment], this.points[iSegment + 1]);
    var segmentEndDistance = segmentBeginDistance + segment.length();
    var segmentEndT = segmentEndDistance / this.length();
    if (t < segmentEndT) {
      var segmentBeginT = segmentBeginDistance / this.length();
      segmentT = ((t - segmentBeginT) * this.length()) / segment.length();
      return segment.lerp(segmentT);
    }
    segmentBeginDistance = segmentEndDistance;
  }
};

/**
 * Unit length tangent vector.
 */
geometry2d.PolyLine.prototype.tangent = function(t) {
  return this.segment(t).direction();
};

/**
 * Unit length normal vector: tangent rotated 1/4 turn counter-clockwise.
 */
geometry2d.PolyLine.prototype.normal = function(t) {
  return this.segment(t).normal();
};

/**
 * Vector projected onto polyline tangent.
 */
geometry2d.PolyLine.prototype.tangentComponent = function(t, vector) {
  return this.tangent(t).scale(geometry2d.dot(vector, this.tangent(t)));
};

/**
 * Vector projected onto polyline normal.
 */
geometry2d.PolyLine.prototype.normalComponent = function(t, vector) {
  return this.normal(t).scale(geometry2d.dot(vector, this.normal(t)));
};

/**
 * Magnitude of vector projected onto polyline tangent.
 */
geometry2d.PolyLine.prototype.tangentComponentMagnitude = function(t, vector) {
  return Math.abs(geometry2d.dot(vector, this.tangent(t)));
};

/**
 * Magnitude of vector projected onto polyline normal.
 */
geometry2d.PolyLine.prototype.normalComponentMagnitude = function(t, vector) {
  return Math.abs(geometry2d.dot(vector, this.normal(t)));
};

/**
 * Magnitude of the tangent component divided by the length of the polyline.
 */
geometry2d.PolyLine.prototype.parametricMagnitude = function(t, vector) {
  return geometry2d.dot(vector, this.tangent(t)) / this.length();
};

return geometry2d;

});
